#include "OpenglAppFramework/inc/OpenglAppFramework.h"
#include "OpenglAppFramework/inc/SceneConfig.h"
#include "OpenglAppFramework/inc/Utils.h"
#include "OpenglAppFramework/inc/glm/gtc/matrix_transform.hpp"

#include <iostream>

void TransformInfo::ToModelMat(const TransformInfo& ts_info, glm::mat4& model_mat) {
    model_mat = glm::translate(glm::mat4(1), ts_info.location);
    model_mat = glm::scale(model_mat, ts_info.scale);
    model_mat = glm::rotate(model_mat, ts_info.rotation_angle, ts_info.rotation_axis);
}

GameObject::GameObject() {    
    m_material = nullptr;
    m_mesh = nullptr;
    m_camera = nullptr;
    m_light_source = nullptr;
    m_instancing_plugin = nullptr;
    m_model_mat = glm::mat4(1.0f);
    m_is_transform_changed = true; // 默认需要重新算一次model_mat
    m_is_static = false;    // 暂时没用
    m_is_cast_shadow = true; // 默认所有物体都会产生阴影
}

GameObject::~GameObject() {
    if(m_material != nullptr) { delete m_material; }
    if(m_mesh != nullptr) { delete m_mesh; }
    if(m_camera != nullptr) { delete m_camera; }
    if(m_light_source != nullptr) { delete m_light_source; }
}

GameObject* GameObject::CreateGameObjectByJson(Scene* scene, const std::string& name, const nlohmann::json& js) {
    // transform
    TransformInfo trans;
    json2TransformInfo(js["transform"], trans);
    auto it = js.find("is_cast_shadow");
    bool is_cast_shadow = it == js.end() ? true : (*it).get<bool>();
    it = js.find("is_static");
    bool is_static = it == js.end() ? false : (*it).get<bool>();
    
    GameObject* obj;
    obj = new GameObject();
    obj->set(scene, name, trans, is_static, is_cast_shadow);

    // plugins
    PlugIn* plugin;
    for(auto& plugin_it : js["plugins"].items()) {
        std::cout << "        create plugin: " << plugin_it.key() << std::endl;
        plugin = PlugIn::CreatePlugInByJson(plugin_it.key(), plugin_it.value());
        if(plugin == nullptr) {
            std::cout << "create plugin failed: " << plugin_it.key() << std::endl;
            delete obj;
            return nullptr;
        }
        plugin->setGameObject(obj);
        obj->setPlugIn(plugin);// CHECK_OPENGL(__FILE__,__LINE__); // ???!!!
    }

    return obj;
}

void GameObject::set(Scene* scene, const std::string& name, const TransformInfo& trans, bool is_static, bool is_cast_shadow) {
    m_scene = scene;
    m_name = name;
    m_transform = trans;
    m_is_static = is_static;
    m_is_cast_shadow = is_cast_shadow;
}

void GameObject::updateModelMat() {
    if(m_is_transform_changed == true) {
        TransformInfo::ToModelMat(m_transform, m_model_mat);
        m_is_transform_changed = false;
    }
}

int GameObject::initAllPlugIns() {
    for(auto kv : m_plugins) {
        PlugIn* plugin = kv.second;
        // 先只初始化mesh
        if(plugin->type() != PlugInType::Mesh) {
            continue;
        }
        if(plugin->init() != 0) {
            return -1;
        }
    }
    for(auto kv : m_plugins) {
        PlugIn* plugin = kv.second;
        // 初始化mesh以外的plugin
        if(plugin->type() == PlugInType::Mesh) {
            continue;
        }
        if(plugin->init() != 0) {
            return -1;
        }
    }

    return 0;
}

void GameObject::setPlugIn(PlugIn* plugin) {
    m_plugins[plugin->name()] = plugin;
    changePlugIn(plugin);
}

void GameObject::changePlugIn(PlugIn* plugin) {
    // Mesh, Material, ParallelLight, Camera, Actions, Instancing, 
    PlugIn* old_plugin = nullptr;
    switch (plugin->type()) {
    case PlugInType::Mesh:          
        old_plugin = m_mesh;
        m_mesh = dynamic_cast<Mesh*>(plugin);
        break;
    case PlugInType::Material:
        old_plugin = m_material;
        m_material = dynamic_cast<Material*>(plugin);
        break;
    case PlugInType::Instancing:
        old_plugin = m_instancing_plugin;
        m_instancing_plugin = dynamic_cast<InstancingPlugIn*>(plugin);
        break;
    case PlugInType::ParallelLight: 
        old_plugin = m_light_source; 
        m_light_source = dynamic_cast<LightSource*>(plugin);
        break;
    case PlugInType::Camera:
        old_plugin = m_camera;
        m_camera = dynamic_cast<Camera*>(plugin);
        break;
    case PlugInType::Actions:
        old_plugin = m_action;
        m_action = dynamic_cast<ActionsPlugIn*>(plugin);
        break;
    case PlugInType::LOD:
        old_plugin = m_lod;
        m_lod = dynamic_cast<LODPlugIn*>(plugin);
        break;
    default: break;
    }
    if(old_plugin != nullptr) {
        old_plugin->setEnable(false);
    }
    plugin->setEnable(true);
}

void GameObject::setPlugIns(const std::vector<PlugIn*>& plugins) {
    for(PlugIn* plugin : plugins) {
        setPlugIn(plugin);
    }
}

void GameObject::setMesh(PlugIn* plugin) {
    m_mesh = dynamic_cast<Mesh*>(plugin);
}

void GameObject::setMaterial(PlugIn* plugin) {
    m_material = dynamic_cast<Material*>(plugin);
}

void GameObject::setCamera(PlugIn* plugin) {
    m_camera = dynamic_cast<Camera*>(plugin);
}

void GameObject::setLightSource(PlugIn* plugin) {
    m_light_source = dynamic_cast<LightSource*>(plugin);;
}

void GameObject::setLocation(const glm::vec3& loc) {
    m_transform.location = loc;
    m_is_transform_changed = true;
}

void GameObject::setRotation(const glm::vec3& aixs, float degree) {
    m_transform.rotation_angle = degree;
    m_transform.rotation_axis = aixs;
    m_is_transform_changed = true;
}

void GameObject::setScale(const glm::vec3& scale) {
    m_transform.scale = scale;
    m_is_transform_changed = true;
}

void GameObject::uploadModelMat(Shader* s) {
    s->setMat4("model_mat", m_model_mat);
    // std::cout<<m_name<<" model_mat"<<std::endl<<mat2str(m_model_mat)<<std::endl;
}

const std::string& GameObject::name() const {
    return m_name;
}

const TransformInfo& GameObject::transformInfo() const {
    return m_transform;
}

const glm::mat4& GameObject::modelMat() const {
    return m_model_mat;
}

bool GameObject::isStatic() const {
    return m_is_static;
}

bool GameObject::isInstancing() const {
    return m_instancing_plugin != nullptr;
}

bool GameObject::isCastShadow() const {
    return m_is_cast_shadow;
}

Mesh* GameObject::getMesh() {
    return m_mesh;
}

Material* GameObject::getMaterial() {
    return m_material;
}

LightSource* GameObject::getLightSource() {
    return m_light_source;
}

Camera* GameObject::getCamera() {
    return m_camera;
}

InstancingPlugIn* GameObject::getInstancingPlugIn() {
    return m_instancing_plugin;
}

PlugIns& GameObject::plugIns() {
    return m_plugins;
}

PlugIn* GameObject::getPlugInByName(const std::string& name) {
    auto it = m_plugins.find(name);
    if(it == m_plugins.end()) {
        return nullptr;
    }
    return it->second;
}

Scene* GameObject::scene() {
    return m_scene;
}

TextureManager& GameObject::textureManger() {
    return m_texutre_manager;
}

void GameObject::render() {
    if(m_mesh == nullptr
    || (m_material == nullptr && m_instancing_plugin == nullptr)) {
        return;
    }
    uint32_t primit_vtx_cnt = m_mesh->getPrimitVertexCount();
    glBindVertexArray(m_mesh->getVAOId());CHECK_OPENGL(__FILE__,__LINE__);
    if(m_mesh->hasIndexBuffer()) {
        if(m_instancing_plugin != nullptr) {
            // 实例化 draw_call
        }
        else {
            glDrawElements(GL_TRIANGLES, primit_vtx_cnt, GL_UNSIGNED_INT, (void*)0);
        }
    }
    else {
        if(m_instancing_plugin != nullptr) {
            glDrawArraysInstanced(GL_TRIANGLES, 0, primit_vtx_cnt, m_instancing_plugin->getInstanceCount());
        }
        else {
            glDrawArrays(GL_TRIANGLES, 0, primit_vtx_cnt);
        }
    }CHECK_OPENGL(__FILE__,__LINE__);
}
